/**
 * \file devices.cpp
 * \brief Parallel and roundrobin device definitions
 * \author Wouter Caarls <w.caarls@tudelft.nl> 
 *
 * \verbatim
 * Copyright (c) 2005-7 Wouter Caarls, Delft University of Technology
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * \endverbatim
 */

#include "des.hpp"
#include "devices.hpp"
#include "_des.hpp"
#include "CStr.h"

/* ************************************************************************ */

void DESparallelDevice::addProcess(DESprocess* process)
{
  process->m_device = this;

  if (!m_facility && DESgetTrace())
  {
    m_facility = lt_symbol_add(DESgetTrace(), CStr(m_name) << ".all" , 0, 0, 0, LT_SYM_F_BITS);

    if (m_facility)
      lt_emit_value_int(DESgetTrace(), m_facility, 0, 0);
    else
      std::cerr << "Couldn't register parallel device facility " << m_name.c_str() << std::endl;
  }

  DESevent event(DESevent::EventStart, 0, NULL, process);
  DESaddEvent(event);
}

void DESparallelDevice::processEvent(DESevent event)
{
  event.m_process->switchToProcessContext(event);
}

DESevent DESparallelDevice::wait(DESprocess *process, DESsignal *signal)
{
  signal->addListener(process);
  process->switchToMainContext();

  if (process->m_lastEvent.m_type != DESevent::EventSignal)
  {
    std::cerr << "Wait returned with a wrong event" << std::endl;
    return DESevent(DESevent::EventNone, 0, NULL, process);
  }
  else
    return process->m_lastEvent;
}

DESevent DESparallelDevice::wait(DESprocess *process, TIME time)
{
  DESevent event(DESevent::EventAwake, time, NULL, process);

  DESaddEvent(event);

  m_processing++;
  if (m_facility)
    lt_emit_value_int(DESgetTrace(), m_facility, 0, m_processing);

  process->switchToMainContext();

  m_processing--;
  if (m_facility)
    lt_emit_value_int(DESgetTrace(), m_facility, 0, m_processing);

  if (process->m_lastEvent.m_type != DESevent::EventAwake)
  {
    std::cerr << "Wait returned with a wrong event" << std::endl;
    return DESevent(DESevent::EventNone, 0, NULL, process);
  }
  else
    return process->m_lastEvent;
}

void DESparallelDevice::exit(DESprocess* process)
{
  while (1)
  {
    process->switchToMainContext();
    std::cerr << "Returned to a zombie process" << std::endl;
  }
}

/* ************************************************************************ */

DESthread::DESthread(std::string name, void *argument) : DESprocess(name, argument),
							 m_rrDevice(0),
							 m_state(StateNew),
							 m_id(0)
{
}

DESevent DESthread::sleep(TIME time)
{
  if (!m_rrDevice)
  {
    std::cerr << "Cannot sleep as thread is not bound to a DESroundRobinDevice"
	      << std::endl;
    return wait(time);
  }
  else
    return m_rrDevice->sleep(this, time);
}

std::ostream& operator<<(std::ostream &s, DESthread::StateType t)
{
  switch (t)
  {
    case DESthread::StateNew:
      return s << "StateNew";
    case DESthread::StateRunnable:
      return s << "StateRunnable";
    case DESthread::StateRunning:
      return s << "StateRunning";
    case DESthread::StateWaiting:
      return s << "StateWaiting";
    case DESthread::StateZombie:
      return s << "StateZombie";
  }

  return s;
}

/* ************************************************************************ */

void DESroundRobinDevice::addProcess(DESthread* process)
{
  process->m_device = this;
  process->m_rrDevice = this;
  process->m_state = DESthread::StateNew;
  process->m_id = m_processes.size();

  m_processes.push_back(process);

  DESevent event(DESevent::EventStart, 0, NULL, process);
  DESaddEvent(event);

  if (!m_facility && DESgetTrace())
  {
    m_facility = lt_symbol_add(DESgetTrace(), CStr(m_name) << ".all", 0, 0, 0, LT_SYM_F_BITS);

    if (m_facility)
      lt_emit_value_int(DESgetTrace(), m_facility, 0, 0);
    else
      std::cerr << "Couldn't register roundrobin device facility " << m_name.c_str() << std::endl;
  }

  if (!process->m_facility && DESgetTrace())
  {
    process->m_facility =
      lt_symbol_add(DESgetTrace(), CStr(m_name) << "." << process->m_name, 0, 0, 0, LT_SYM_F_BITS);

    if (process->m_facility)
    {
      lt_emit_value_int(DESgetTrace(), process->m_facility, 0, 0);
    }
    else
      std::cerr << "Couldn't register thread facility " << process->m_name.c_str() << std::endl;
  }
}

DESevent DESroundRobinDevice::wait(DESprocess *process, DESsignal *signal)
{
  DESthread *rrprocess = dynamic_cast<DESthread*>(process);

  if (!rrprocess)
  {
    std::cerr << "Can only use DESthreads in a DESroundRobinDevice" << std::endl;    
  }
  else if (rrprocess->m_state != DESthread::StateRunning)
  {
    std::cerr << "Cannot wait in state " << rrprocess->m_state << std::endl;
  } else if (m_currentProcess != rrprocess->m_id)
  {
    std::cerr << "Internal error: current process is not the running process"
	      << std::endl;
  }
  else
  {
    signal->addListener(process);

    rrprocess->m_state = DESthread::StateWaiting;
    m_currentProcess = -1;

    DESevent event(DESevent::EventSchedule, 0, NULL, process);
    DESaddEvent(event);

    process->switchToMainContext();


    if (process->m_lastEvent.m_type != DESevent::EventSignal)
    {
      std::cerr << "Wait returned with a wrong event" << std::endl;
    }
    else
      return process->m_lastEvent;  
  }

  return DESevent(DESevent::EventNone, 0, NULL, process);
}

DESevent DESroundRobinDevice::wait(DESprocess *process, TIME time)
{
  DESthread *rrprocess = dynamic_cast<DESthread*>(process);

  if (!rrprocess)
  {
    std::cerr << "Can only use DESthreads in a DESroundRobinDevice" << std::endl;    
  }
  else if (rrprocess->m_state != DESthread::StateRunning)
  {
    std::cerr << "Cannot wait in state " << rrprocess->m_state << std::endl;
  } else if (m_currentProcess != rrprocess->m_id)
  {
    std::cerr << "Internal error: current process is not the running process"
	      << std::endl;
  }
  else
  {
    DESevent event(DESevent::EventAwake, time, NULL, process);
    DESaddEvent(event);

    m_lastProcessTime = DESgetCurrentTime();

    m_processing++;
    if (m_facility)
      lt_emit_value_int(DESgetTrace(), m_facility, 0, m_processing);

    if (process->m_facility)
      lt_emit_value_int(DESgetTrace(), process->m_facility, 0, 1);

    process->switchToMainContext();
  
    if (process->m_facility)
      lt_emit_value_int(DESgetTrace(), process->m_facility, 0, 0);

    m_processing--;
    if (m_facility)
      lt_emit_value_int(DESgetTrace(), m_facility, 0, m_processing);

    if (process->m_lastEvent.m_type != DESevent::EventAwake)
    {
      std::cerr << "Wait returned with a wrong event" << std::endl;
    }
    else
      return process->m_lastEvent;
  }

  return DESevent(DESevent::EventNone, 0, NULL, process);
}

void DESroundRobinDevice::exit(DESprocess* process)
{
  DESthread *rrprocess = dynamic_cast<DESthread*>(process);

  if (!rrprocess)
  {
    std::cerr << "Can only use DESthreads in a DESroundRobinDevice" << std::endl;    
  }
  else if (rrprocess->m_state != DESthread::StateRunning)
  {
    std::cerr << "Cannot wait in state " << rrprocess->m_state << std::endl;
  } else if (m_currentProcess != rrprocess->m_id)
  {
    std::cerr << "Internal error: current process is not the running process"
	      << std::endl;
  }
  else
  {
    rrprocess->m_state = DESthread::StateZombie;
    m_currentProcess = -1;

    DESevent event(DESevent::EventSchedule, 0, NULL, process);
    DESaddEvent(event);
  }
  
  while (1)
  {
    process->switchToMainContext();
    std::cerr << "Returned to a zombie process" << std::endl;
  }
}

DESevent DESroundRobinDevice::sleep(DESthread *process, TIME time)
{
  DESthread *rrprocess = process;

  if (!rrprocess)
  {
    std::cerr << "Can only use DESthreads in a DESroundRobinDevice" << std::endl;    
  }
  else if (rrprocess->m_state != DESthread::StateRunning)
  {
    std::cerr << "Cannot sleep in state " << rrprocess->m_state << std::endl;
  } else if (m_currentProcess != rrprocess->m_id)
  {
    std::cerr << "Internal error: current process is not the running process"
	      << std::endl;
  }
  else
  {
    if (time != 0)
    {
      rrprocess->m_state = DESthread::StateWaiting;
      DESaddEvent(DESevent(DESevent::EventAwake, time, NULL, process));
    }
    else
      rrprocess->m_state = DESthread::StateRunnable;

    m_currentProcess = -1;

    DESevent event(DESevent::EventSchedule, 0, NULL, process);
    DESaddEvent(event);

    process->switchToMainContext();

    if (time != 0 && process->m_lastEvent.m_type != DESevent::EventAwake)
    {
      std::cerr << "Sleep returned with wrong event " << process->m_lastEvent << std::endl;
    }
    else
      return process->m_lastEvent;
  }

  return DESevent(DESevent::EventNone, 0, NULL, process);
}

void DESroundRobinDevice::processEvent(DESevent event)
{
  DESthread *process = dynamic_cast<DESthread*>(event.m_process);

  if (!process)
  {
    std::cerr << "Can only use DESthreads in a DESroundRobinDevice" << std::endl;    
  }
  else  
  {
    switch (event.m_type)
    {
      case DESevent::EventNone:
	std::cerr << "Unknown event type in " << event << std::endl;
	break;
      case DESevent::EventStart:
	if (process->m_state != DESthread::StateNew)
	{
	  std::cerr << "Cannot process " << event << " in state "
		    << process->m_state << std::endl;
	}
	else
	{
	  if (m_currentProcess >= 0)
	  {
	    process->m_state = DESthread::StateRunnable;
	    process->m_lastEvent = event;
	  }
	  else
	  {	    
	    m_currentProcess = process->m_id;
	    process->m_state = DESthread::StateRunning;
	    event.m_process->switchToProcessContext(event);
	  }
	}
	break;
      case DESevent::EventSignal:
	if (process->m_state != DESthread::StateWaiting)
	{
	  std::cerr << "Cannot process " << event << " in state "
		    << process->m_state << std::endl;
	} else
	{
	  if (m_currentProcess >= 0)
	  {
	    process->m_state = DESthread::StateRunnable;
	    process->m_lastEvent = event;
	  }
	  else
	  {
	    m_currentProcess = process->m_id;
	    process->m_state = DESthread::StateRunning;
	    event.m_process->switchToProcessContext(event);
	  }
	}
	break;
      case DESevent::EventAwake:
	if (process->m_state != DESthread::StateRunning &&
	    process->m_state != DESthread::StateWaiting)
	{
	  std::cerr << "Cannot process " << event << " in state "
		    << process->m_state << std::endl;
	} else if (process->m_state == DESthread::StateRunning)
	{
	  if (m_currentProcess != process->m_id)
	  {
	    std::cerr << "Internal error: current process is not the running process"
		      << std::endl;
	  }
	  else
	  {
            m_processedTime += event.m_time - m_lastProcessTime;
	    event.m_process->switchToProcessContext(event);
	  }
	} else if (process->m_state == DESthread::StateWaiting)
	{
	  if (m_currentProcess >= 0)
	  {
	    process->m_state = DESthread::StateRunnable;
	    process->m_lastEvent = event;
	  }
	  else
	  {
	    m_currentProcess = process->m_id;
	    process->m_state = DESthread::StateRunning;
	    event.m_process->switchToProcessContext(event);
	  }
	}
	break;
      case DESevent::EventSchedule:
	/* Only schedule if no process is currently running */
	if (m_currentProcess == -1)
	{
	  for (int ii=(process->m_id+1)%m_processes.size();
	       ii != process->m_id;
	       ii=(ii+1)%m_processes.size())
	  {
	    if (m_processes[ii]->m_state == DESthread::StateRunnable)
	    {
	      m_currentProcess = ii;
	      m_processes[ii]->m_state = DESthread::StateRunning;
	      m_processes[ii]->switchToProcessContext(m_processes[ii]->m_lastEvent);
	      return;
	    }
	  }
	
	  /* No other runnable processes */
	  if (process->m_state == DESthread::StateRunnable)
	  {
	    m_currentProcess = process->m_id;
	    process->m_state = DESthread::StateRunning;
	    process->switchToProcessContext(process->m_lastEvent);
	  }
	}
	break;
    }
  }
}
